Skip to content

S02-01 面向对象-类和对象

[TOC]

概述

入门案例:养猫猫问题

养猫猫问题:张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色;另一只叫小花,今年 100 岁,花色。编写程序,当用户输入小猫的名字时,显示该猫的名字、年龄、颜色;若输入错误,显示“张老太没有这只猫猫”。

方案:现有技术

方式 1:单独定义变量
java
// 第1只猫信息
String cat1Name = "小白";
int cat1Age = 3;
String cat1Color = "白色";
// 第2只猫信息
String cat2Name = "小花";
int cat2Age = 100;
String cat2Color = "花色";

缺点:不利于数据管理,变量过多易混乱。

方式 2:使用数组
java
// 第1只猫信息
String[] cat1 = {"小白", "3", "白色"};
// 第2只猫信息
String[] cat2 = {"小花", "100", "花色"};

缺点

  1. 数据类型不明确(年龄是字符串类型)
  2. 只能通过下标获取信息,对应关系不清晰
  3. 不能体现猫的行为(如叫、跑)
现有技术缺点

现有技术不利于数据管理、效率低,引出面向对象编程(OOP)

方案:面向对象

实现思路:定义Cat类(自定义数据类型),包含属性(名字、年龄、颜色)和行为,通过创建对象管理每只猫的信息。

java
public class Object01 {
    // 编写一个main方法
    public static void main(String[] args) {
        // 2. 实例化 Cat 类
        // 实例化第一只猫(创建对象),并赋值给 cat1 变量
        Cat cat1 = new Cat();
        cat1.name = "小白";
        cat1.age = 3;
        cat1.color = "白色";
        cat1.weight = 10;
        // 实例化第二只猫,并赋值给 cat1 变量
        Cat cat2 = new Cat();
        cat2.name = "小花";
        cat2.age = 100;
        cat2.color = "花色";
        cat2.weight = 20;

        // 3. 访问对象属性
        System.out.println("第1只猫信息:" + cat1.name + " " + cat1.age + " " + cat1.color + " " + cat1.weight);
        System.out.println("第2只猫信息:" + cat2.name + " " + cat2.age + " " + cat2.color + " " + cat2.weight);
    }
}

// 1. 定义猫类 Cat(自定义数据类型)
class Cat {
    // 属性(成员变量)
    String name; // 名字
    int age; // 年龄
    String color; // 颜色
    double weight; // 体重
    // 行为(后续补充)
}

面向对象编程

面向对象编程(OOP,Object-Oriented Programming):是一种以对象为核心的编程范式,它将现实世界中的事物抽象为程序中的对象,通过封装对象的属性和行为、建立对象间的继承与多态关系,来组织和构建程序。

Java 是纯粹的面向对象语言(除基本数据类型外,所有事物都可抽象为对象,且基本类型也有对应的包装类),OOP 是 Java 的核心思想,其核心在于类与对象,并通过封装、继承、多态三大特性实现代码的高复用、易维护和可扩展。

类与对象

类(Class):对象的 “模板” 或 “蓝图”,是对一类具有相同 属性(状态) 和方法(行为) 的事物的抽象描述

  • 属性:也叫成员变量 / 字段(Field),表示对象的状态(比如人的姓名、年龄)。
  • 方法:也叫成员方法(Method),表示对象的行为(比如人的吃饭、跑步)。

对象(Object)类的 “实例”,是类的具体实现,是内存中实际存在的实体。通过new关键字可以创建类的对象,每个对象都拥有类定义的属性和方法的独立副本。

类与对象的关系图

猫类(Cat)→ 提取共性:属性(name, age, color)、行为(run, cry...)

对象1(小白):name="小白",age=3,color="白色"

对象2(小花):name="小花",age=100,color="花色"

对象内存图

对象在内容中的存在形式如图:

image-20251217165829493

基础

属性

属性(成员变量,字段,Field):表示对象的状态(比如人的姓名、年龄)。

定义语法

访问修饰符:public、protected、默认、private,用于控制属性的访问范围。

java
访问修饰符 属性类型 属性名;

// 示例
class Car {
  protected String name;
}

注意事项

  1. 属性的类型:属性可以是基本数据类型(int、double)或引用类型(对象、数组)。

    java
    class Car {
        // 基本类型
        double price;
    
        // 引用类型
        String name;
        String color;
        String[] masters;
    }
  2. 属性的默认值:未赋值的属性有默认值,规则同数组

    • 基本类型:
      • int 0short 0byte 0long 0
      • float 0.0double 0.0
      • char \u0000
      • boolean false
    • 引用类型(如 String):null
    java
    // 属性的默认值
    public class PropertiesDetail {
        public static void main(String[] args) {
            Person p1 = new Person();
            System.out.println(
            	"age=" + p1.age + " name=" + p1.name + " sal=" + p1.sal + " isPass=" + p1.isPass
            ); // age=0,name=null,sal=0.0,isPass=false
        }
    }
    
    class Person {
        int age; // 默认值:0
        String name; // 默认值:null
        double sal; // 默认值:0.0
        boolean isPass; // 默认值:false
    }

对象创建

对象创建有以下 2 种方式:

  1. 方式 1:先声明再创建

    java
    Cat cat; // 声明对象(栈中存储引用)
    cat = new Cat(); // 创建对象(堆中分配空间)
  2. 方式 2:直接创建

    java
    Cat cat = new Cat();

访问属性

语法

java
对象名.属性名;

示例:属性赋值/取值

java
cat.name = "小白"; // 赋值
System.out.println(cat.age); // 取值

内存流程图:对象创建

示例代码

java
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
Person p2 = p1; // p2指向p1引用的对象
System.out.println(p2.age); // 输出10(p1和p2操作同一个对象)

创建对象的流程

Person p = new Person();为例:

  1. 加载Person类信息(属性和方法,仅加载一次)。
  2. 在堆中分配空间,进行默认初始化(属性赋默认值)。
  3. 将堆中对象地址赋给栈中的引用变量p
  4. 进行指定初始化(如p.name = "jack")。

内存流程图

  1. Person p1 = new Person();

    image-20251217175222183

  2. p1.age = 10; p1.name = "小明";

    image-20251217174623501

  3. Person p2 = p1;

    image-20251217174706845

  4. System.out.println(p2.age);

    image-20251217174856697

练习:对象内存流程图

练习:画出以下代码的内存流程图

java
Person a = new Person();
a.age = 10;
a.name = "小明";

Person b;
b = a;
System.out.println(b.name); // "小明"

b.age = 200;
b = null;
System.out.println(a.age); // 200
System.out.println(b.age); // 抛出异常:java.lang.NullPointerException

image-20251217180753920

匿名对象

匿名对象(Anonymous Object):就是创建对象时,没有把它的内存地址赋给任何一个变量(即没有给它起名字)

普通对象 vs 匿名对象

语法对比:普通对象 vs 匿名对象:

我们先来看最直观的代码对比。假设有一个 Person 类,里面有一个 work() 方法。

1. 普通对象(有名对象):

java
// 创建对象,并把地址赋给变量 p (p 就是这个对象的名字)
Person p = new Person();

// 通过名字 p 去调用方法
p.work();
// 如果想再调用一次,还可以继续用 p
p.work();

2. 匿名对象:

java
// 直接 new 出来,连名字都不起,直接跟着一个点(.)去调用方法
new Person().work();

核心特征

匿名对象的核心特征:

  1. 只能使用一次,无法循环使用: 因为你没有用变量去记录它的内存地址,所以这行代码执行完之后,你就再也找不到这个对象了。如果你写了两行 new Person().work();,那是在内存里创建了两个完全不同的对象,分别调用了一次方法。

  2. 生命周期极短(光速被回收):

    由于匿名对象在使用完毕后没有任何变量指向它,它会立刻变成“内存垃圾”。Java 的垃圾回收器 (GC) 会在合适的时候迅速把它清理掉,这在一定程度上节省了内存。

常见使用场景

常见的使用场景:

在实际开发中,我们通常在以下三种场景下使用匿名对象,目的是为了让代码更简洁

场景 1:只需要调用一次对象的方法时:

如果你创建一个对象仅仅是为了调用它里面的某一个方法,而且以后再也不用它了,写成匿名对象最省事。

java
// 传统写法:啰嗦
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();

// 匿名对象写法:一气呵成
int num = new Scanner(System.in).nextInt();

如果涉及到为对象赋值,不要使用匿名对象

image-20260226172525621

场景 2:作为方法的参数传递(最常用):

当你调用一个方法,这个方法需要接收一个对象作为参数,而你刚好只需要传进去,不需要在外面继续保留这个对象时。

java
class MathUtil {
    public static void printInfo(Person p) {
        p.work();
    }
}

public class Main {
    public static void main(String[] args) {
        // 传统写法
        Person p1 = new Person();
        MathUtil.printInfo(p1);

        // 匿名对象写法:直接把 new 出来的东西当作参数塞进去
        MathUtil.printInfo(new Person());
    }
}

场景 3:作为方法的返回值:

当一个方法需要返回一个对象时,可以直接 return new 对象(),不用在方法里多定义一个变量。

java
public Person getPerson() {
    // 传统写法
    // Person p = new Person();
    // return p;

    // 匿名对象写法
    return new Person();
}

匿名对象 vs 匿名内部类

很多初学者会把匿名对象匿名内部类 (Anonymous Inner Class) 搞混,它们名字很像,但完全不是一个东西:

  • 匿名对象: 只是单纯的 new 了一个已经存在的类,不给它分配变量名。(如上文所述)。
  • 匿名内部类: 是在 new 的同时,重写(或实现)了某个类/接口的方法。它不但没有变量名,连具体的类名都没有

匿名内部类示例(对比用):

java
// 这是在创建一个没有名字的、实现了 Runnable 接口的子类对象
new Runnable() {
 @Override
 public void run() {
     System.out.println("这是一个匿名内部类");
 }
}.run();

成员方法

方法(Method,成员方法):表示对象的行为(比如人的吃饭、跑步)。

快速入门

Person 类添加方法:

  1. speak():输出“我是一个好人”
  2. cal01():计算 1+2+...+1000 的结果
  3. cal02(int n):计算 1+2+...+n 的结果
  4. getSum(int num1, int num2):计算两个数的和
java
public class Method01 {
    public static void main(String[] args) {
        // 创建Person对象
        Person p1 = new Person();

        // 调用方法
        p1.speak(); // 输出"我是一个好人"
        p1.cal01(); // 输出1+...+1000的结果
        p1.cal02(5); // 输出1+...+5的结果
        p1.cal02(10); // 输出1+...+10的结果
        int sum = p1.getSum(10, 20); // 接收方法返回值
        System.out.println("两数之和:" + sum);
    }
}

class Person {
    String name;
    int age;

    // 1. speak方法:无参数、无返回值
    public void speak() {
        System.out.println("我是一个好人");
    }

    // 2. cal01方法:无参数、无返回值
    public void cal01() {
        int res = 0;
        for (int i = 1; i <= 1000; i++) {
            res += i;
        }
        System.out.println("1+...+1000的结果:" + res);
    }

    // 3. cal02方法:有参数、无返回值
    public void cal02( int n ) { // 形参
        int res = 0;
        for (int i = 1; i <= n; i++) {
            res += i;
        }
        System.out.println("1+...+" + n + "的结果:" + res);
    }

    // 4. getSum方法:有参数、有返回值
    public int getSum(int num1, int num2) { // 形参列表
        return num1 + num2;  // 返回值
    }
}

语法结构

语法格式

一个完整的 Java 成员方法由多个组成部分构成,语法格式如下:

java
[修饰符列表] [返回值类型] 方法名([参数列表]) [throws 异常类型列表] {
    // 方法体:具体的功能逻辑代码
    [return 返回值]
}

下面对每个组成部分进行详细解析:

修饰符列表

修饰符分为访问修饰符非访问修饰符,可组合使用(需遵循语法规则,比如abstractstatic不能同时修饰一个方法)。

访问修饰符
修饰符可见范围说明
public所有类(跨包、跨类)最宽松的访问权限
protected本类、同包子类、不同包子类继承相关的访问权限
default本类、同包类(无修饰符)默认访问权限
private本类最严格的访问权限
非访问修饰符
修饰符作用
static定义类方法(静态方法),属于类而非对象
final方法不能被子类重写(Override)
abstract定义抽象方法,无方法体,仅声明方法签名(只能在抽象类/接口中)
native本地方法,方法体由非 Java 代码(如 C/C++)实现,仅声明签名
synchronized同步方法,保证多线程环境下的原子性(避免线程安全问题)
strictfp严格浮点计算,保证不同平台下浮点运算结果一致

返回值

核心特性

方法执行完成后返回给调用者的结果类型,分为两种情况:

  1. 有返回值

    1. 可以是 Java 的基本数据类型(int/double等)、引用数据类型(String/Object/自定义类等)。

    2. 此时方法体中必须通过return语句返回对应类型(或类型兼容)的值,且return后不能有多余代码。

      image-20251229143340624

      image-20251229143420893

      image-20251229143608526

  2. 无返回值:用void表示,方法体中可以省略return语句(或写return;表示结束方法)。

    java
    // 无返回值:仅打印信息
    public void printInfo(String msg) { 
        System.out.println(msg);
        // 可省略return,也可写return;
    }
  3. 返回值数量:一个方法最多有一个返回值,如需返回多个结果,可使用数组

    image-20251229142527474

    image-20251229142829554

方法名

遵循 Java 的命名规范

  • 采用小驼峰命名法(首字母小写,后续单词首字母大写,如eatFoodcalculateSum)。
  • 见名知意,准确描述方法的功能(如getAge表示获取年龄,setName表示设置姓名)。
  • 不能与关键字重名,且不能包含空格特殊符号(除_$)。

参数列表

基本格式

基本格式:多个参数用逗号分隔,每个参数的格式为:

java
// 基本格式
参数类型 参数名

// 示例
int a, String b

核心特性

  1. 参数个数:一个方法可以有 0 个参数,也可以有多个参数,参数中间用 , 逗号间隔。

  2. 参数类型:参数可以为任意类型(包括基本类型和引用类型)。

  3. 形参/实参

    • 实参调用方法时传递给形参的具体值。
    • 形参定义方法时的参数称为形参,
    • 要求实参的类型、个数、顺序必须与形参兼容(自动类型转换除外,如int可赋值给double)。
    java
    class AA {
        // 1. 方法定义(形参)
        public int[] getSumAndSub(int n1, int n2) {  // 形参
            int[] resArr = new int[2];
            resArr[0] = n1 + n2;
            resArr[1] = n1 - n2;
            return resArr;
        }
    }
    
    public class MethodDetail {
        public static void main(String[] args) {
            // 2. 方法调用(实参)
            AA a = new AA();
    
            // ✅ 兼容:int -> int
            a.getSumAndSub(10, 4);  // 实参
    
            // ✅ 兼容:byte -> int
            byte b1 = 1, b2 = 2;
            a.getSumAndSub(b1, b2);  // 实参
    
            // ❌ 不兼容:double -> int
            a.getSumAndSub(1.1, 1.2);  // 实参
        }
    }
可变参数(不定长参数)

JDK 5 引入的特性,允许方法接收任意个数的同类型参数,语法为:参数类型... 参数名(本质是数组)。

  • 可变参数必须放在参数列表的最后一位
  • 一个方法只能有一个可变参数。

示例

java
// 可变参数:计算多个int的和
public int sum(int... nums) { 
    int total = 0;
    for (int num : nums) { // 可变参数可当作数组遍历
        total += num;
    }
    return total;
}

// 调用:可传入任意个数的int
sum(1, 2); // 结果3
sum(1, 2, 3, 4); // 结果10

异常声明(throws)

声明方法可能抛出的受检异常(Checked Exception),告诉调用者需要处理这些异常(要么try-catch捕获,要么继续throws)。

示例

java
// 声明方法可能抛出IOException
public void readFile(String path) throws IOException { 
    // 读取文件的逻辑,可能抛出IOException
}

方法体

方法的核心逻辑代码块,包含变量定义、语句执行、流程控制(分支、循环)、调用其他方法等。

核心特性

  1. 访问成员/局部变量

    • 方法体中可以访问类的成员变量(实例变量/静态变量)和局部变量(方法内定义的变量)。

    • 局部变量必须先声明并初始化才能使用,而成员变量有默认值(如int默认 0,String默认null)。

  2. 方法不能嵌套定义(不同于 JS

    java
    class Demo {
        // ❌ 方法不能嵌套定义
        public void fn1() {
            public void fn2() {}
        }
    }

内存流程图:方法调用

示例代码

java
public static void main(String[] args) {
  Person p1 = new Person();
  int sum = p1.getSum(10, 20);
  System.out.println("两数之和:" + sum);
}

class Person {
  public int getSum(int num1, int num2) {
    return num1 + num2;
  }
}

方法调用流程

  1. 执行方法时,JVM 会开辟独立的栈空间(方法栈)。
  2. 方法执行完毕或遇到 return 时,栈空间释放,返回调用处。
  3. 返回后继续执行方法后面的代码。
  4. 当 main 方法(栈)执行完毕后,整个程序退出。

内存流程图

成员方法作用

成员方法作用

  1. 提高代码复用性:避免重复编写相同逻辑。
  2. 封装实现细节:用户无需关心内部逻辑,直接调用。

示例:封装二维数组遍历方法

定义MyTools类的printArr方法,实现二维数组遍历:

java
public class Method02 {
    public static void main(String[] args) {
        int[][] map = {{0,0,1},{1,1,1},{1,1,3}};
        // 2. 多次调用方法,复用代码
        MyTools tool = new MyTools();
        tool.printArr(map);
        tool.printArr(map);
    }
}

class MyTools {
    // 1. 封装方法:遍历二维数组
    public void printArr(int[][] map) {
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + "\t");
            }
            System.out.println();
        }
    }
}

方法调用

方法调用注意事项

  1. 本类中方法调用:直接调用(如sayOk()调用print())。

    java
    class A {
        public void print(int n) { 
            System.out.println("print方法被调用,n=" + n);
        }
    
        public void sayOk() {
            print(10); // 本类中调用:直接调用
        }
    }
  2. 跨类方法调用:通过对象名调用(如B类对象调用A类方法)。

    java
    class A {
        public void m1() {
            // 跨类调用:通过B类对象名调用
            B b = new B();
            b.hi(); 
        }
    }
    
    class B {
        public void hi() { 
            System.out.println("B类的hi方法被执行");
        }
    }
  3. 注意:访问修饰符

    跨类的方法调用和方法的访问呢修饰符也有关系。

练习

  1. 练习 1:判断奇偶方法

    编写AA类的isOdd(int num)方法:判断一个数是奇数还是偶数,返回boolean

    java
    // 定义方法:判断奇偶
    public boolean isOdd(int num) {
      return num % 2 != 0;
    }
    
    // 调用方法
    System.out.println(a.isOdd(5)); // true
    System.out.println(a.isOdd(4)); // false
  2. 练习 2:打印图形方法

    编写AA类的print(int row, int col, char c)方法:根据行、列、字符,打印对应格式的图形。

    java
    // 定义方法:打印图形
    public void print(int row, int col, char c) {
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                System.out.print(c);
            }
            System.out.println(); // 换行
        }
    }
    
    // 调用方法
    a.print(4, 4, '#');

方法传参机制@

Java 的方法传参机制是面试高频考点,也是理解方法调用底层逻辑的核心。核心结论:Java 中只有一种传参方式 —— 值传递(Pass by Value),不存在 “引用传递(Pass by Reference)”。很多开发者误以为引用类型是 “引用传递”,本质是对 “值” 的定义理解偏差:引用类型传递的是 “对象引用地址的副本”,而非引用本身,这仍属于值传递范畴。

值传递的本质

值传递的核心

调用方法时,JVM 会创建实参的副本,并将这个副本传递给方法的形参;方法内部对形参的所有修改,仅作用于副本,不会直接影响原始实参。

对比容易混淆的 “引用传递”(Java 不支持)

引用传递是将实参的内存地址直接传递给形参,形参和实参指向同一个内存位置,方法内修改形参会直接改变实参。

基本数据类型

底层原理

基本数据类型(int/double/boolean等)的实参存储在栈内存中,传参时会复制实参的数值本身给形参:

  • 形参是栈中新创建的局部变量,与实参完全独立;

  • 方法内修改形参的数值,仅改变栈中副本,原实参不受影响。

代码示例 + 内存解析

java
public class BasicTypeParam {
    // 方法:修改形参a的值
    public static void changeInt(int a) {
        a = 10; // 仅修改栈中形参副本
        System.out.println("方法内a的值:" + a); // 输出:10
    }

    public static void main(String[] args) {
        int num = 5; // 实参num:栈中存储值5
        changeInt(num); // 传递num的副本(5)给形参a
        System.out.println("方法外num的值:" + num); // 输出:5(原实参未变)
    }
}

内存执行流程(文字图解)

  1. main方法执行时,栈中创建变量num,赋值为5(栈地址:比如0x001,值:5);
  2. 调用changeInt(num)时,JVM 在栈中为形参a分配新空间(栈地址:0x002),将num5复制给a
  3. 方法内执行a=10,仅修改0x002地址的值为100x001地址的num仍为5
  4. 方法执行完毕,形参a出栈销毁,main方法中num的值保持5

引用数据类型

底层原理

引用数据类型(自定义类 / 数组 / 集合 / String 等)的实参存储逻辑:

  • 栈内存:存储对象的引用地址(指向堆内存中的实际对象);

  • 堆内存:存储对象的属性 / 数据。

传参时,JVM 复制的是栈中的引用地址副本给形参:

  • 形参和实参的地址副本指向同一个堆对象

  • 方法内通过地址副本修改堆对象的属性:会影响原对象(因为指向同一个堆内存);

  • 方法内修改形参的地址指向(比如new新对象):仅修改副本的地址,原实参的地址不变,原对象不受影响。

image-20260302153901415

场景 1:修改堆对象的属性(影响原对象)

java
// 自定义引用类型
class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class RefTypeParam1 {
    // 方法:修改对象的age属性
    public static void changePersonAttr(Person p) {
        p.age = 20; // 通过地址副本修改堆对象属性
        System.out.println("方法内p的age:" + p.age); // 输出:20
    }

    public static void main(String[] args) {
        // 实参person:栈存地址0x003,堆存{name:"张三", age:18}
        Person person = new Person("张三", 18);
        changePersonAttr(person); // 传递地址副本0x003给形参p
        System.out.println("方法外person的age:" + person.age); // 输出:20(原对象被修改)
    }
}

内存执行流程

  1. main中创建Person对象:栈中person存地址0x003,堆中0x003地址存储{name:"张三", age:18}
  2. 调用方法时,形参p得到地址副本0x003,此时personp都指向堆中同一个对象;
  3. 方法内p.age=20:直接修改堆中0x003地址的age属性,原personage同步变化;
  4. 方法结束后,p出栈,person仍指向0x003age20

场景 2:修改引用地址的指向(不影响原对象)

java
public class RefTypeParam2 {
    // 方法:修改形参p的地址指向
    public static void changePersonRef(Person p) {
        // 形参p指向新的堆对象(地址0x004)
        p = new Person("李四", 25);
        System.out.println("方法内p的name:" + p.name); // 输出:李四
    }

    public static void main(String[] args) {
        Person person = new Person("张三", 18); // 指向0x003
        changePersonRef(person); // 传递0x003给p
        System.out.println("方法外person的name:" + person.name); // 输出:张三(原对象未变)
    }
}

内存执行流程

  1. mainperson指向堆地址0x003(张三,18);
  2. 方法调用时,p得到0x003的副本,此时pperson都指向0x003
  3. 方法内p = new Person(...)p的地址改为0x004(李四,25),但person仍指向0x003
  4. 方法结束后,p出栈,person的地址和指向的对象均未变化。

不可变类型

StringIntegerDouble等包装类属于不可变类型:对象一旦创建,内部属性(如Stringvalue数组、Integervalue)无法修改,所有 “修改” 操作本质是创建新对象。

String 传参示例

java
public class StringParam {
    public static void changeString(String s) {
        s = "李四"; // 不是修改原对象,而是创建新String对象(地址0x005)
        System.out.println("方法内s的值:" + s); // 输出:李四
    }

    public static void main(String[] args) {
        String str = "张三"; // 栈中str指向常量池地址0x004("张三")
        changeString(str); // 传递0x004给s
        System.out.println("方法外str的值:" + str); // 输出:张三(原对象未变)
    }
}

Integer 包装类传参示例

java
public class IntegerParam {
    public static void changeInteger(Integer i) {
        i = 20; // 自动装箱,创建新Integer对象(地址0x006)
        System.out.println("方法内i的值:" + i); // 输出:20
    }

    public static void main(String[] args) {
        Integer num = 10; // 指向常量池地址0x005(10)
        changeInteger(num);
        System.out.println("方法外num的值:" + num); // 输出:10
    }
}

核心原因

不可变类型的 “修改” 本质是重新赋值,即让形参指向新对象,但原实参的地址仍指向旧对象,因此无法影响原实参。

命令行参数

概述

你一定在 Java 中写过无数次 psvm,并且生成过下面这行代码:

java
public static void main(String[] args) { ... }

你有没有好奇过,括号里的 String[] args 到底是干什么用的?

其实,这就是 命令行参数 (Command-Line Arguments)。它允许你在启动程序的那一瞬间,从外部向程序内部传递一些信息,而不需要修改代码并重新编译。

核心本质普通的字符串数组

  • 本质:它就是一个普通的字符串数组 (String Array)。
  • 名字args 只是 arguments(参数)的缩写。你完全可以把它改成 String[] myData,程序一样能跑,只是业界习惯叫 args
  • 来源:当你通过命令行执行 java 类名 时,跟在类名后面的内容,就会被 Java 虚拟机 (JVM) 自动收集起来,塞进这个数组里。

接收命令行参数

代码实战:如何接收参数:

假设我们写了一个简单的程序,用来给指定的用户发送问候语。

java
public class Greeting {
    public static void main(String[] args) {
        // 1. 先检查有没有传参数进来
        if (args.length == 0) {
            System.out.println("请在启动时提供用户名!");
            return; // 结束程序
        }

        // 2. 获取第一个参数 (数组索引从 0 开始)
        String username = args[0];
        System.out.println("你好," + username + "!欢迎学习 Java。");

        // 3. 打印所有参数看看
        for (String arg : args) {
            System.out.println("-> " + arg);
        }
        System.out.println("你一共传入了 " + args.length + " 个参数:");
    }
}

传递命令行参数

场景 A:终端

场景 A:在真实命令行/终端中运行:

这是最原汁原味的方式。编译好 .class 文件后,在黑框框(终端)里输入:

  • 输入命令java Greeting 张三 李四 王五
  • 解析规则:Java 会以空格作为分隔符。
  • 最终结果
    • args[0]"张三"
    • args[1]"李四"
    • args[2]"王五"
场景 B:IDEA

场景 B:在 IntelliJ IDEA 中运行 (开发测试时):

在 IDEA 里,我们通常直接点绿色三角运行,没有机会敲命令行。这时候需要配置:

  1. 在代码编辑区右上角,找到当前运行的类名,点击旁边的下拉箭头。

  2. 选择 Edit Configurations... (编辑配置)。

  3. 在弹出的窗口中,找到 Program arguments (程序参数) 这一栏。

  4. 在里面输入你的参数:张三 李四 王五(中间用空格隔开)。

  5. 点击 OK 保存,然后再次运行程序即可。

image-20260302155637546

注意事项

坑 1:参数里本身就有空格怎么办:

如果你的参数是一个完整的文件路径,比如 C:\Program Files\Java。如果你直接传,Java 会把它当成两个参数:C:\ProgramFiles\Java

  • 解决办法:用双引号把整个参数包起来。
  • 正确传法java MyApp "C:\Program Files\Java"

坑 2:忘了转换数据类型:

不管你在命令行输入的是不是数字,Java 接收到的 永远都是字符串 (String)。如果你输入了 java Calculator 10 20,你想把它们相加,直接写 args[0] + args[1] 会得到 "1020"(字符串拼接)。

  • 解决办法:必须手动将字符串解析为其他类型。
  • 代码示例int num1 = Integer.parseInt(args[0]);

坑 3:越界异常 (ArrayIndexOutOfBoundsException):

这是新手最容易犯的错误。你直接在代码里写了 System.out.println(args[0]);,但是启动程序的时候忘记传参数了。此时 args 是个空数组(长度为 0),去拿第 0 个元素就会导致程序崩溃。

  • 解决办法:永远在处理参数前,先判断 args.length > 0

访问修饰符

Java 提供 4 种访问修饰符(Modifier),其访问范围与包密切相关。以下是类和成员的访问权限对比表:

类的访问修饰符

类只能用 publicdefault(无修饰符)修饰,访问范围如下:

修饰符同包可见不同包可见核心说明
public所有包可访问,一个 .java 文件只能有一个 public 类,且类名与文件名一致
default仅同包可访问,无修饰符的类属于默认访问权限

类成员的访问修饰符

类的成员(变量、方法、内部类)可使用 4 种修饰符,访问范围如下:

修饰符本类同包不同包子类所有包核心说明
private仅本类可访问,与包无关
default仅同包可访问,无修饰符
protected同包 + 不同包子类可访问
public所有包可访问

示例

  1. 示例 1:protected 跨包访问

    protected 修饰的成员,不同包的子类只能通过子类实例访问,不能通过父类实例访问。

    java
    // 父类:com.demo.parent
    package com.demo.parent;
    
    public class Parent {
        protected String name = "Parent";
    }
    java
    // 子类:com.demo.child(不同包)
    package com.demo.child;
    
    import com.demo.parent.Parent;
    
    public class Child extends Parent { 
        public void test() {
            // 1. ✅ 合法:子类实例访问 protected 成员
            System.out.println(this.name); // Parent
    
            // 2. ✅ 合法:子类实例访问
            Child child = new Child();
            System.out.println(child.name); 
    
            // 3. ❌ 错误:不同包不能通过父类实例访问 protected 成员
            Parent parent = new Parent();
            // System.out.println(parent.name);
        }
    }
  2. 示例 2:四种访问修饰符的访问范围

    java
    package com.hspedu.modifier;
    
    public class A {
        // 修饰属性
        public int n1 = 100;
        protected int n2 = 200;
        int n3 = 300;
        private int n4 = 400;
    
        // 修饰方法
        public void m1() {}
        protected void m2() {}
        void m3() {}
        private void m4() {}
    
        public void hi() {
            // 1. ✅ 同类可访问所有属性和方法
            System.out.println(n1 + " " + n2 + " " + n3 + " " + n4);
            m1(); m2(); m3(); m4();
        }
    }
    java
    // 同包:
    package com.hspedu.modifier;
    public class B {
        public void say() {
            A a = new A();
            // 2. ✅ 同包可访问:public/protected/default属性和方法
            System.out.println(a.n1 + " " + a.n2 + " " + a.n3);
            a.m1(); a.m2(); a.m3();
    
            // 3. ❌ 同包类不可访问:private属性和方法
            // System.out.println(a.n4);
            // a.m4();
        }
    }
    java
    // 不同包:
    package com.hspedu.pkg;
    public class C {
        public void say() {
            A a = new A();
            // 2. ✅ 不同包可访问:public属性和方法
            System.out.println(a.n1);
            a.m1();
    
            // 3. ❌ 不同包不可访问:protected/default/private属性和方法
            // System.out.println(a.n2 + " " + a.n3 + "" + a.n4);
            //  a.m2(); a.m3();a.m4();
        }
    }

方法重载

概述

方法重载(Method Overload):在同一个类中,存在多个方法满足以下条件:

  • 方法名称完全相同
  • 参数列表必须不同(参数个数、参数类型、参数顺序,三者满足其一即可);
  • 与参数名、返回值类型、访问修饰符、抛出异常列表无关

本质:编译时静态绑定

Java 编译器在编译阶段(而非运行阶段),会根据调用方法时传入的实参的静态类型(声明类型),匹配对应的重载方法并确定调用目标,这个过程称为 “静态绑定”。

方法重写(Override):是运行时动态绑定,根据对象的实际类型确定调用方法。

重载的核心价值

  • 语义统一:同一类操作(如 “加法”“打印”)用同一个方法名,符合 “见名知意” 的编码习惯;
  • 简化调用:调用者无需记忆不同功能的方法名,仅需传入不同参数即可;
  • 适配多场景:支持同一逻辑适配不同输入参数(如计算不同个数的数值和、处理不同类型的数据源)。

快速入门

java
public class OverLoad01 {
    // 1. 两个int的和
    public int calculate(int n1, int n2) { 
        System.out.println("调用:两个int的和");
        return n1 + n2;
    }

    // 2. int+double的和
    public double calculate(int n1, double n2) { 
        System.out.println("调用:int+double的和");
        return n1 + n2;
    }

    // 3. double+int的和
    public double calculate(double n1, int n2) { 
        System.out.println("调用:double+int的和");
        return n1 + n2;
    }

    // 4. 三个int的和
    public int calculate(int n1, int n2, int n3) { 
        System.out.println("调用:三个int的和");
        return n1 + n2 + n3;
    }

    public static void main(String[] args) {
        System.out.println(calculate(1, 2)); // 调用两个int参数的方法
        System.out.println(calculate(1, 2.1)); // 调用int+double的方法
        System.out.println(calculate(1.1, 2)); // 调用double+int的方法
        System.out.println(calculate(1, 2, 3)); // 调用三个int参数的方法
    }
}

编译器匹配优先级

编译器在匹配重载方法时,遵循 “精确匹配 → 自动类型转换匹配 → 可变参数匹配” 的优先级,逐级匹配,直到找到唯一匹配的方法;若匹配到多个或无匹配,均会报错。

  • 优先级 1:精确匹配(最优先)

    实参类型与形参类型完全一致,直接匹配:

    java
    public class OverloadMatch {
        public static void print(int a) {
            System.out.println("int类型:" + a);
        }
    
        public static void print(double a) {
            System.out.println("double类型:" + a);
        }
    
        public static void main(String[] args) {
            print(10);    // 精确匹配print(int) → int类型:10
            print(10.5);  // 精确匹配print(double) → double类型:10.5
        }
    }
  • 优先级 2:自动类型转换匹配

    若无精确匹配,编译器会尝试自动向上转型(如 int → long → float → doublechar → int)匹配:

    注意:若存在多个可转换的匹配(如同时有 longfloat 重载),编译器会报错(模糊的方法调用)。

    java
    public class OverloadMatch {
        public static void print(long a) {
            System.out.println("long类型:" + a);
        }
    
        public static void print(double a) {
            System.out.println("double类型:" + a);
        }
    
        public static void main(String[] args) {
            print(10); // int无精确匹配,自动转long → long类型:10
            print('a');// char无精确匹配,自动转int→再转long → long类型:97
        }
    }
  • 优先级 3:可变参数匹配(最低)

仅当精确匹配、自动转换匹配均失败时,才会匹配可变参数(...)的重载方法:

java
public class OverloadMatch {
    public static void print(int... nums) {
        System.out.println("可变参数:" + Arrays.toString(nums));
    }

    public static void main(String[] args) {
        print(10, 20); // 无精确匹配,匹配可变参数 → 可变参数:[10, 20]
    }
}

练习

练习 1:判断题

java
与 `void show(int a, char b, double c)` 构成重载的有(b c d e):
- a) void show(int x, char y, double z){} // 否(形参列表完全相同)
- b) int show(int a, double c, char b){} // 是(形参顺序不同)
- c) void show(int a, double c, char b){} // 是(形参顺序不同)
- d) boolean show(int c, char b){} // 是(形参个数不同)
- e) void show(double c){} // 是(形参个数、类型不同)
- f) double show(int x, char y, double z){} // 否(形参列表完全相同)
- g) void shows(){} // 否(方法名不同)

练习 2:重载方法实现

  1. 定义Methods类的 3 个重载方法m

    • m(int n):输出 n 的平方。
    • m(int n1, int n2):输出 n1×n2 的结果。
    • m(String str):输出字符串。
    java
    class Methods {
        // 1. 定义 m 方法的重载
        public void m(int n) {
            System.out.println("平方=" + (n * n));
        }
    
        public void m(int n1, int n2) {
            System.out.println("相乘=" + (n1 * n2));
        }
    
        public void m(String str) {
            System.out.println("传入的str=" + str);
        }
    }
    
    public class OverLoadExercise {
        public static void main(String[] args) {
            Methods method = new Methods();
            // 2. 测试 m 方法
            method.m(10); // 输出:平方=100
            method.m(10, 20); // 输出:相乘=200
            method.m("韩顺平教育hello"); // 输出:传入的str=韩顺平教育hello
        }
    }
  2. 定义 3 个重载方法max

    • max(int n1, int n2):返回两个 int 的最大值。
    • max(double n1, double n2):返回两个 double 的最大值。
    • max(double n1, double n2, double n3):返回三个 double 的最大值。
    java
    class Methods {
        // 1. 定义 max 方法的重载
        public int max(int n1, int n2) {
            return n1 > n2 ? n1 : n2;
        }
    
        public double max(double n1, double n2) {
            return n1 > n2 ? n1 : n2;
        }
    
        public double max(double n1, double n2, double n3) {
            double max1 = n1 > n2 ? n1 : n2;
            return max1 > n3 ? max1 : n3;
        }
    }
    
    public class OverLoadExercise {
        public static void main(String[] args) {
            Methods method = new Methods();
            // 2. 测试 max 方法
            System.out.println(method.max(10, 24)); // 24
            System.out.println(method.max(10.0, 21.4)); // 21.4
            System.out.println(method.max(10.0, 1.4, 30)); // 30.0
        }
    }

可变参数

概述

可变参数(Variable Arguments,Varargs):是方法参数的一种特殊形式,通过 类型名... 参数名 声明,允许调用方法时传入 0 个、1 个或多个同类型参数,编译器会自动将这些参数封装为一个数组,方法内部可直接将可变参数当作数组处理。

核心本质:数组的 “语法糖”

可变参数并非新的参数类型,而是 JDK 为简化数组参数调用设计的语法糖 —— 编译阶段,编译器会将 类型... 参数名 转换为 类型[] 参数名,方法调用时传入的零散参数会被自动打包为数组。

反编译后显示为数组

image-20260228170019652

语法格式

可变参数声明在方法参数列表中,格式为:

java
[修饰符] 返回值类型 方法名([固定参数列表], 类型... 可变参数名) { 
    // 方法体:可变参数当作数组处理
}

快速入门

实现计算任意个数的 int 类型之和。

java
public class VarParameter01 {
    // 可变参数sum:接收任意个数的int,使用时可以当做数组来用
    public int sum(int... nums) { 
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            res += nums[i];
        }
        return res;
    }

    public static void main(String[] args) {
        System.out.println(sum(1, 5, 100)); // 106(3个数之和)
        System.out.println(sum(1, 19)); // 20(2个数之和)
        System.out.println(sum()); // 0(0个数之和)
    }
}

核心使用规则(必守)

核心使用规则(必守)

  • 规则 1:可变参数必须是参数列表的最后一个

    可变参数接收 “任意数量” 的参数,若后面还有其他参数,编译器无法区分可变参数的结束位置,因此编译报错:

    java
    public class VarargsRule1 {
        // ✅ 正确:可变参数在最后
        public static void print(String prefix, int... nums) {} 
    
        // ❌ 错误:可变参数后有其他参数(编译报错:Varargs parameter must be the last parameter)
        public static void print(int... nums, String suffix) {}
    }
  • 规则 2:一个方法只能有一个可变参数

    同理,多个可变参数会导致编译器无法拆分参数边界,编译报错:

    java
    public class VarargsRule2 {
        // ❌ 错误:一个方法多个可变参数(编译报错:Variable arity parameter must be the last parameter)
        public static void sum(int... nums1, double... nums2) {}
    }
  • 规则 3:可变参数可接收 0 个参数

    调用时不传可变参数,编译器会传入一个空数组(而非 null),方法内部遍历不会报空指针:

    java
    public class VarargsRule3 {
        public static void print(int... nums) {
            System.out.println("数组长度:" + nums.length); // 0个参数时输出0
            for (int num : nums) { // 遍历空数组,无异常
                System.out.println(num);
            }
        }
    
        public static void main(String[] args) {
            print(); // 数组长度:0
        }
    }
  • 规则 4:可变参数支持基本类型和引用类型

    • 基本类型可变参数:int...double... 等;
    • 引用类型可变参数:String...Person... 等。
    java
    class Person {
        private String name;
        public Person(String name) { this.name = name; }
    }
    
    public class VarargsType {
        // 引用类型可变参数
        public static void printPersons(Person... persons) {
            for (Person p : persons) {
                System.out.println(p.name);
            }
        }
    
        public static void main(String[] args) {
            printPersons(new Person("张三"), new Person("李四"));
        }
    }
  • 规则 5:可直接传递数组给可变参数

    编译器兼容数组参数,无需手动拆分,等价于传入多个零散参数:

    java
    public class VarargsArray {
        public static int sum(int... nums) {
            int total = 0;
            for (int num : nums) total += num;
            return total;
        }
    
        public static void main(String[] args) {
            int[] arr = {1,2,3};
            // 直接传数组,等价于sum(1,2,3)
            System.out.println(sum(arr)); // 6
        }
    }

底层实现原理

JVM 本身不支持可变参数,其实现完全依赖编译器的 “语法糖转换”,核心步骤:

  1. 编译方法时:将 类型... 参数名 替换为 类型[] 参数名,方法签名变为 方法名(类型[])
  2. 编译方法调用时
    • 若传入零散参数(如 sum(1,2,3)),自动打包为数组 new int[]{1,2,3}
    • 若传入数组(如 sum(arr)),直接传递数组引用,不做额外处理;
    • 若传入 0 个参数,自动创建空数组 new int[0]

反编译验证(使用 javap -c 命令):

编译 sum(int... nums) 后,字节码中方法签名为 sum([I)I[I 表示 int 数组),与 sum(int[] nums) 完全一致。

练习

封装一个可变参数方法showScore,接收姓名和任意门课成绩,返回“姓名+课程数+总分”。

java
public class VarParameterExercise {
    public String showScore(String name, double... scores) {
        double totalScore = 0;
        for (int i = 0; i < scores.length; i++) {
            totalScore += scores[i];
        }
        return name + " 有" + scores.length + "门课,总分为:" + totalScore;
    }

    public static void main(String[] args) {
        System.out.println(showScore("milan", 90.1, 80.0));
        System.out.println(showScore("terry", 90.1, 80.0, 10, 30.5, 70));
    }
}

作用域

概述

作用域(Scope):是 Java 中程序元素(变量、方法、类、接口等)的可见范围生命周期的集合,核心作用是控制元素的访问权限和内存管理。其中,变量的作用域是最核心、最易混淆的部分(方法 / 类的作用域主要由访问修饰符决定)。

作用域的关键维度

  • 可见性(Visibility):程序的哪个部分能访问该元素(如 “方法内的变量仅方法内可见”)
  • 生命周期(Lifecycle):元素的创建 / 销毁时机(如 “局部变量随方法调用创建,调用结束销毁”)

作用域分类

作用域的核心分类(按范围从小到大)

plaintext
块级作用域 < 方法/构造器作用域 < 类级作用域(成员变量) < 包级作用域 < 全局作用域(public类/方法)

块级作用域

块级作用域(Block Scope):在代码块{} 包裹的区域)内声明的变量,包括:

  • 条件块(if/elseswitch);
  • 循环块(forwhiledo-while);
  • 同步块(synchronized);
  • 普通代码块(直接用 {} 包裹的代码)。

核心特性

  • 生命周期:进入块时在栈内存创建,退出块时立即销毁(栈帧弹出)
  • 可见性:仅在当前块及嵌套的子块内可见,块外无法访问
  • 默认值:无默认值,必须显式初始化后才能使用(否则编译报错)
  • 命名规则:同一块内不能声明同名变量;块嵌套时,内层变量可覆盖外层同名变量(就近原则)

示例

  1. 普通块作用域

    java
    public class BlockScopeDemo {
        public static void main(String[] args) {
            // 外层块
            {
                int a = 10; // 块级变量,作用域:外层块
                System.out.println(a); // 合法:10
    
                // 嵌套块
                {
                    int b = 20; // 作用域:嵌套块
                    System.out.println(a + b); // 合法:30(内层可访问外层)
                    int a = 30; // 合法:内层覆盖外层同名变量(就近原则)
                    System.out.println(a); // 30(访问内层a)
                }
                // System.out.println(b); // 错误:b仅在嵌套块内可见
            }
            // System.out.println(a); // 错误:a仅在外层块内可见
        }
    }
  2. 循环 / 条件块作用域

    java
    public class BlockScopeDemo2 {
        public static void main(String[] args) {
            // for循环块:i的作用域仅在循环内
            for (int i = 0; i < 3; i++) {
                System.out.println(i); // 合法
            }
            // System.out.println(i); // 错误:i超出作用域
    
            // if块:flag的作用域仅在if内
            if (true) {
                boolean flag = true;
                System.out.println(flag); // 合法
            }
            // System.out.println(flag); // 错误:flag超出作用域
        }
    }

方法/构造器作用域

方法 / 构造器作用域(Method/Constructor Scope):在方法 / 构造器内声明的局部变量(包括方法的形参),是块级作用域的 “超集”(方法体本身就是一个大代码块)。

核心特性

  • 生命周期:方法 / 构造器调用时创建(栈帧压入),执行完毕后销毁(栈帧弹出)
  • 可见性:整个方法 / 构造器内可见(包括内部嵌套块),方法外无法访问
  • 默认值:无默认值,必须显式初始化(形参由调用者传值初始化)
  • 命名规则:方法内局部变量不能与形参同名;方法内嵌套块可覆盖方法级变量

示例:方法局部变量 + 形参

java
public class MethodScopeDemo {
    public static int add(int a, int b) { // 1. 方法形参(属于方法作用域)
        int sum = a + b; // 2. 方法级局部变量
        if (sum > 10) {
            int temp = sum * 2; // 嵌套块变量,仅if内可见
            sum = temp;
        }
        // System.out.println(temp); // 错误:temp超出作用域
        return sum;
    }

    public static void main(String[] args) {
        int result = add(3, 5);
        // System.out.println(a); // 错误:形参a超出方法作用域
        // System.out.println(sum); // 错误:sum超出方法作用域
    }
}

类级作用域(成员变量)

类级作用域(成员变量,属性):在类内、方法 / 块外声明的变量(类的成员),分为两类:

  • 实例变量(非静态)
  • 静态变量(类变量)
实例变量

实例变量(Instance Variable)特性

  • 修饰符:无 static 修饰,可加 private/public/protected 等访问修饰符
  • 生命周期:对象创建时在堆内存分配,对象被 GC 回收时销毁
  • 可见性:整个类内可见;外部需通过对象实例访问(obj.var),受访问修饰符限制
  • 默认值:有默认值(同数组规则:int→0,String→null,boolean→false 等)
  • 线程安全:非线程安全(每个对象有独立副本)

示例:实例变量

java
public class ClassScopeDemo {
    // 1. 实例变量(对象级,每个对象独立)
    private String name; 
    private int age; // 默认值0

    public ClassScopeDemo(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void show() {
        // 2. 类内可直接访问成员变量
        System.out.println("姓名:" + name + ",年龄:" + age);
    }

    public static void main(String[] args) {
        // 3. 类外:必须通过对象访问实例变量
        ClassScopeDemo obj1 = new ClassScopeDemo("张三", 20);
        ClassScopeDemo obj2 = new ClassScopeDemo("李四", 25);
        obj1.show(); // 姓名:张三,年龄:20
        obj2.show(); // 姓名:李四,年龄:25
    }
}
静态变量

静态变量(Class Variable)特性

  • 修饰符static 修饰,可加访问修饰符
  • 生命周期:类加载时在方法区分配,类卸载(JVM 退出)时销毁
  • 可见性:整个类内可见;外部可通过类名 / 对象实例访问(Class.var),受访问修饰符限制
  • 默认值:有默认值(同数组规则:int→0,String→null,boolean→false 等)
  • 线程安全:线程不安全(所有对象共享同一个副本)

示例:静态变量

java
public class ClassScopeDemo {
    // 1. 静态变量(类级,所有对象共享)
    private static String className = "Java基础"; 

    public void show() {
        // 2. 类内可直接访问成员变量(与实例变量相同)
        System.out.println("类名:" + className);
    }

    public static void main(String[] args) {
        // 3. 类外静态变量:通过类名访问(推荐)
        System.out.println(ClassScopeDemo.className); // Java基础

        // 4. 静态变量共享:修改后所有对象可见
        ClassScopeDemo.className = "Java进阶";
        obj1.show(); // 类名:Java进阶
        obj2.show(); // 类名:Java进阶
    }
}

异常参数作用域

异常参数作用域(Catch Scope)catch 块中声明的异常参数(如 catch (Exception e)),属于特殊的块级作用域。

核心特性

  • 生命周期catch 块执行时创建,执行完毕后销毁
  • 可见性:仅在当前 catch 块内可见
  • 命名规则:同一 try-catch 的多个 catch 块可声明同名异常参数(不同作用域)

示例:catch 块异常参数

java
public class CatchScopeDemo {
    public static void main(String[] args) {
        try {
            int a = 1 / 0;
        } catch (ArithmeticException e) {
            // e仅在当前catch块内可见
            System.out.println("算术异常:" + e.getMessage());
        } catch (Exception e) {
            // 不同catch块,同名e合法
            System.out.println("通用异常:" + e.getMessage());
        }
        // System.out.println(e); // 错误:e超出catch作用域
    }
}

内部类作用域

内部类的作用域依赖其定义位置,分为:

  • 成员内部类
  • 局部内部类
  • 匿名内部类
成员内部类

成员内部类类内、方法外的内部类;

可见性:可访问外部类的所有成员(包括 private);

外部类访问内部类:需通过内部类实例(Outer.Inner inner = new Outer().new Inner())。

示例:成员内部类

java
public class InnerClassScopeDemo {
    private String outerVar = "外部类变量";

    // 1. 成员内部类
    class MemberInner {
        public void show() {
            // 可访问外部类private成员
            System.out.println(outerVar); // 外部类变量
        }
    }

    public static void main(String[] args) {
        InnerClassScopeDemo outer = new InnerClassScopeDemo();
        // 2. 成员内部类:外部需通过实例访问
        MemberInner memberInner = outer.new MemberInner();
        memberInner.show();
    }
}
局部内部类

局部内部类方法 / 块内的内部类;

可见性:仅在方法 / 块内可见;

访问规则:可访问外部类的成员,以及外部方法的 final/effectively final 局部变量(JDK 8+)。

示例:局部内部类

java
public class InnerClassScopeDemo {
    private String outerVar = "外部类变量";

    // 方法内局部内部类
    public void testLocalInner() {
        // effectively final变量(未重新赋值)
        String localVar = "方法局部变量";
        // 1. 局部内部类
        class LocalInner {
            public void show() {
                System.out.println(outerVar); // 访问外部类成员
                System.out.println(localVar); // 访问外部方法final变量
            }
        }

        // 2. 局部内部类仅方法内可见
        LocalInner inner = new LocalInner();
        inner.show();
    }

    public static void main(String[] args) {
        InnerClassScopeDemo outer = new InnerClassScopeDemo();
        outer.testLocalInner();
    }
}
匿名内部类

匿名内部类:无类名的局部内部类;

作用域:与局部内部类一致,仅在定义的方法 / 块内有效;

访问规则:同局部内部类。

作用域规则总结

作用域规则总结

  1. 默认值

    • 块级/方法/构造器:没有默认值,必须显式初始化后才能使用(否则编译报错)
    • 类级(实例/静态):有默认值,规则与数组一致(int→0,String→null,boolean→false 等)
  2. 变量重名

    • 同一块内:不能声明同名变量;

      java
      public void hi() {
          String address = "北京";
          // String address = "上海"; // ❌ 错误(同一作用域局部变量重名)
      }
    • 块嵌套时:内层变量可覆盖外层同名变量(就近原则)

      java
      class Person {
          String name = "jack";
      
          public void say() {
              String name = "king"; // 局部变量与属性重名
              System.out.println("say() name=" + name); // 就近原则,输出king
          }
      }
  3. 生命周期

    • 类级(实例/静态):伴随对象创建而创建,伴随对象销毁而销毁。
    • 块级/方法/构造器:伴随代码块执行而创建,伴随代码块结束而销毁。
    java
    class Person {
        // 1. 类级变量 name 是随着 Person 对象的创建而创建,随着 Person 对象的销毁而销毁(持续时间更久)
        String name = "jack";
    
        public void say() {
            // 2. 局部变量 name 会随着 say() 调用而创建,也会随着 say() 调用结束而销毁
            String name = "king";
        }
    }
  4. 访问范围

    • 类级(实例/静态):整个类内可见;外部需通过类名 / 对象实例访问(Class.var / obj.var),受访问修饰符限制。
    • 块级/方法/构造器:整个块 / 方法 / 构造器内可见(包括内部嵌套块),块 / 方法外无法访问。
    java
    class Person {
        String name = "jack";
    
        public void say() {
            String age = "king";
        }
    
        public void hi() {
            System.out.println(age); // ❌ 2. 访问失败
        }
    }
    
    class T {
        public void test() {
            Person p1 = new Person();
            System.out.println(p1.name); // ✅ 1. 其他类通过对象访问属性,输出jack
        }
    }
  5. 修饰符

    • 类级(实例/静态):可加 private/public/protected 等访问修饰符。
    • 块级/方法/构造器:不能加访问修饰符。
    java
    class Person {
        // 1. 属性可以添加修饰符
        public String name = "jack";
    
        public void say() {
            // 2. 局部变量不可以添加修饰符
            // public String age = "king";
        }
    }

this

概述

this:是 Java 保留关键字,有两种核心语义:

  • 实例上下文:在实例方法 / 构造方法中,this 指代调用当前方法的对象实例(或正在初始化的对象实例),可通过 this 访问对象的成员变量 / 方法;
  • 构造调用:在构造方法中,this(参数) 用于调用本类的其他构造方法,实现构造逻辑复用。

本质:实例方法的隐式参数

Java 中所有实例方法(非 static 方法)都会隐式接收一个 this 参数,该参数由 JVM 在调用方法时自动传递,指向调用此方法的对象实例。

示例:字节码编译验证

从字节码层面看,实例方法的第一个参数永远是 this(类型为当前类),比如:

java
// 源码
public class Person {
    private String name;
    public void setName(String name) {
        this.name = name;  
    }
}

// 编译后的字节码(简化):setName方法的第一个参数是this
// void setName(LPerson; Ljava/lang/String;)V

JVM 调用 person.setName("张三") 时,会将 person 对象的引用作为 this 参数传递给 setName 方法,因此方法内可通过 this 访问该对象的 name 成员。

image-20260105162935290

使用场景

this 的核心使用场景

  1. 场景 1:区分同名的局部变量(形参)与成员变量(属性)

    这是 this 最常用的场景:当方法的局部变量与类的成员变量同名时,this.成员变量 明确指向成员变量,避免 “变量遮蔽”。

    java
    public class Person {
        private String name;
        private int age;
    
        // 形参name/age与成员变量同名,用this区分
        public Person(String name, int age) {
            // this.name:成员变量;name:形参
            this.name = name;
            this.age = age;
        }
    }
  2. 场景 2:调用本类的实例方法

    this.方法名() 可显式调用本类的其他实例方法,虽然省略 this 也能调用,但显式使用可增强代码可读性(尤其是区分继承的方法时)。

    java
    public class Calculator {
        private int result;
    
        public void add(int num) {
            this.result += num;
        }
    
        public void multiply(int num) {
            // 显式调用本类的add方法(省略this也可,但this增强语义)
            this.add(num);
            this.result *= num;
        }
    }
  3. 场景 3:调用本类的其他构造方法

    构造方法中通过 this(参数) 调用本类的重载构造方法,实现构造逻辑复用核心规则

    • this() 必须是构造方法的第一条语句
    • this()参数列表需匹配本类的某个构造方法;
    • 不能递归调用(如构造 A 调 this(),而 this() 又调回构造 A);
    • 不能与 super() 同时使用(二者都需是第一条语句)。
    • this() 语法只能在构造器中使用,不能在其他成员方法中使用。
    java
    public class User {
        private String username;
        private String password;
        private String email;
    
        // 两参构造:调用三参构造,email传默认值
        public User(String username, String password) {
            this(username, password, "default@xxx.com"); // 必须在第一行
            System.out.println("两参构造执行");
        }
    
        // 核心三参构造:初始化所有成员
        public User(String username, String password, String email) {
            this.username = username;
            this.password = password;
            this.email = email;
            System.out.println("三参构造执行");
        }
    
        public static void main(String[] args) {
            User u2 = new User("李四", "654321");
            // 输出顺序:
            // 三参构造执行 → 两参构造执行
        }
    }
  4. 场景 4:返回当前对象(链式调用)

    在实例方法中返回 this(当前对象引用),可实现 “链式调用”(如 Builder 模式、流式编程),简化代码调用。

    java
    public class StringBuilderDemo {
        private StringBuilder sb = new StringBuilder();
    
        // 返回this,支持链式调用
        public StringBuilderDemo append(String str) {
            sb.append(str);
            return this; // 返回当前对象
        }
    
        public StringBuilderDemo append(int num) {
            sb.append(num);
            return this;
        }
    
        public String getResult() {
            return sb.toString();
        }
    
        public static void main(String[] args) {
            // 链式调用:连续调用append方法
            String result = new StringBuilderDemo()
                    .append("Hello")
                    .append("Java")
                    .append(2025)
                    .getResult();
            System.out.println(result); // HelloJava2025
        }
    }
  5. 场景 5:将当前对象作为参数传递

    this 可作为实参传递给其他方法,将当前对象引用传递出去,适用于 “对象协作” 场景(如回调、依赖注入)。

    java
    // 工具类:接收Person对象并打印信息
    class PersonUtil {
        public static void printPerson(Person person) {
            System.out.println("姓名:" + person.getName() + ",年龄:" + person.getAge());
        }
    }
    
    public class Person {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public void showInfo() {
            // 将当前对象(this)传递给PersonUtil的printPerson方法
            PersonUtil.printPerson(this);
        }
    
        // getter
        public String getName() { return this.name; }
        public int getAge() { return this.age; }
    
        public static void main(String[] args) {
            Person p = new Person("王五", 25);
            p.showInfo(); // 输出:姓名:王五,年龄:25
        }
    }
  6. 场景 6:内部类中访问外部类的 this

    非静态内部类(成员内部类 / 局部内部类)有自己的 this(指向内部类实例),若需访问外部类的 this,需通过 外部类名.this 显式指定。

    java
    public class Outer {
        private String outerVar = "外部类变量";
    
        // 成员内部类
        class Inner {
            private String innerVar = "内部类变量";
            private String outerVar = "内部类覆盖的outerVar";
    
            public void show() {
                // 1. this:指向内部类实例
                System.out.println(this.innerVar); // 内部类变量
                System.out.println(this.outerVar); // 内部类覆盖的outerVar
    
                // 2. Outer.this:指向外部类实例
                System.out.println(Outer.this.outerVar); // 外部类变量
            }
        }
    
        public static void main(String[] args) {
            Outer outer = new Outer();
            Outer.Inner inner = outer.new Inner();
            inner.show();
        }
    }

核心规则

this 的核心规则(必守)

  1. 规则 1:不能在静态上下文使用 this

    静态上下文(静态方法、静态代码块、静态内部类)属于类级别,无对象实例,因此 this 无指向,编译直接报错。

    java
    public class ThisStaticError {
        private static String staticVar = "静态变量";
        private String instanceVar = "实例变量";
    
        // 1. 静态方法:不能使用this
        public static void staticMethod() {
            // System.out.println(this.instanceVar); // 错误:Cannot use this in a static context
            // System.out.println(this); // 错误:同上
        }
    
        // 2. 静态代码块:不能使用this
        static {
            // System.out.println(this.staticVar); // 错误:同上
        }
    
        // 3. 静态内部类:不能访问外部类的this
        static class StaticInner {
            public void show() {
                // System.out.println(ThisStaticError.this.instanceVar); // 错误:静态内部类无外部this
            }
        }
    }
  2. 规则 2:this () 调用构造方法的限制

    • this() 必须是构造方法的第一条语句,否则编译报错;
    • this() 不能递归调用(如 public Person() { this(); } 会无限递归,编译报错);
    • this() 的参数列表必须匹配本类的某个构造方法(参数个数 / 类型 / 顺序一致);
    • 不能同时使用 this()super()(二者都需是第一条语句,冲突)。
    java
    public class ThisConstructorRule {
        public ThisConstructorRule() {
            // System.out.println("非第一条语句"); // 错误:this()必须是第一条
            this(10);
        }
    
        public ThisConstructorRule(int num) {
            // this(); // 错误:递归调用构造方法
        }
    
        // public ThisConstructorRule(String str) {
        //     super(); // 错误:不能同时用super()和this()
        //     this(10);
        // }
    }
  3. 规则 3:this 指向调用方法的对象实例

    同一个类的不同对象调用同一实例方法,this 指向不同的实例,保证每个对象的成员独立。可以通过 .hashCode() 判断 this 指向不同的内存地址。

    java
    public class ThisPointDemo {
        private int num;
    
        public void showThis() {
            System.out.println("this的引用地址:" + this.hashCode());
        }
    
        public static void main(String[] args) {
            ThisPointDemo obj1 = new ThisPointDemo();
            ThisPointDemo obj2 = new ThisPointDemo();
    
            obj1.showThis(); // this指向obj1(地址不同)
            obj2.showThis(); // this指向obj2(地址不同)
        }
    }
  4. 规则 4:this 不能被赋值

    this 是关键字,不是可赋值的变量,试图给 this 赋值会编译报错:

    java
    public class ThisAssignError {
        public void assignThis() {
            // 错误:The left-hand side of an assignment must be a variable
            // this = new ThisAssignError();
        }
    }
  5. 规则 5:this 永远不为 null(合法调用下)

    实例方法必须通过对象调用(obj.method()),JVM 会将 obj 作为 this 传递,因此合法调用下 this 不可能为 null

    例外:通过反射非法调用实例方法时,可传递 null 作为 this,此时方法内使用 this 会抛出 NullPointerException

    java
    import java.lang.reflect.Method;
    
    public class ThisNullReflect {
        public void show() {
            System.out.println(this); // 反射传null时,this为null → NPE
        }
    
        public static void main(String[] args) throws Exception {
            Method method = ThisNullReflect.class.getMethod("show");
            method.invoke(null); // 传递null作为this → 运行时NPE
        }
    }

练习

需求:定义 Person 类,提供compareTo()方法,判断两个 Person 对象的名字和年龄是否完全一致。

java
public class TestPerson {
    public static void main(String[] args) {
        Person p1 = new Person("mary", 20);
        Person p2 = new Person("mary", 20);
        System.out.println("p1 和 p2 比较的结果:" + p1.compareTo(p2)); // true
    }
}

class Person {
    String name;
    int age;

    // 构造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 比较方法
    public boolean compareTo(Person p) {
        // this 代表当前对象(调用compareTo方法的对象)
        return this.name.equals(p.name) && this.age == p.age;
    }
}

本章作业

  1. 编写类 A01,定义方法max(),实现求某个 double 数组的最大值,并返回(Homework01.java)
  2. 编写类 A02,定义方法find(),实现查找某字符串是否在字符串数组中,并返回索引;找不到返回-1(Homework02.java)
  3. 编写类 Book,定义方法updatePrice(),更改书的价格:
    • 价格 >150 → 150
    • 价格 >100 → 100
    • 否则不变(Homework03.java)
  4. 编写类 A03,实现数组复制功能copyArr(),输入旧数组,返回新数组(元素和旧数组一致)(Homework04.java)
  5. 定义圆类 Circle,属性:半径;提供方法:显示圆周长、显示圆面积(Homework05.java)
  6. 创建 Calc 计算类,定义两个操作数,提供加减乘除方法(除数为 0 时提示),创建对象测试(Homework06.java)
  7. 设计 Dog 类,属性:名字、颜色、年龄;定义show()方法显示信息(使用 this.属性)(Homework07.java)
  8. 以下代码编译运行后,输出结果是(10,9,10
    java
    public class Test {
        int count = 9;
        public void count1() {
            count = 10;
            System.out.println("count1=" + count);
        }
        public void count2() {
            System.out.println("count1=" + count++);
        }
        public static void main(String args[]) {
            new Test().count1(); // count1=10
            Test t1 = new Test();
            t1.count2(); // count1=9(后置++,先输出后自增)
            t1.count2(); // count1=10
        }
    }
  9. 定义 Music 类,属性:音乐名 name、时长 times;方法:播放 play()、返回属性信息 getInfo()(Homework09.java)
  10. 以下代码运行结果是(101,100,101,101
    java
    class Demo {
        int i = 100;
        public void m() {
            int j = i++; // j=100,i=101
            System.out.println("i=" + i); // 101
            System.out.println("j=" + j); // 100
        }
    }
    class Test {
        public static void main(String[] args) {
            Demo d1 = new Demo();
            Demo d2 = d1; // 引用传递,指向同一个对象
            d2.m();
            System.out.println(d1.i); // 101
            System.out.println(d2.i); // 101
        }
    }
  11. 调用语句System.out.println(method(method(10.0,20.0),100));编译正确,method方法的定义形式为:
    java
    public double method(double d1, double d2) { ... }
  12. 创建 Employee 类,属性:名字、性别、年龄、职位、薪水;提供 3 个构造器(复用构造器):
    • 构造器 1:初始化所有属性
    • 构造器 2:初始化名字、性别、年龄
    • 构造器 3:初始化职位、薪水(Homework12.java)
  13. 将对象作为参数传递给方法(Homework13.java)
    • 定义 Circle 类:属性 radius,方法findArea()返回面积
    • 定义 PassObject 类:方法printAreas(Circle c, int times),打印 1~times 的半径及对应面积
    • 测试类中调用printAreas(),输出结果如下:
      Radius 1.0 → Area 3.141592653589793
      Radius 2.0 → Area 12.566370614359172
      Radius 3.0 → Area 28.274333882308138
      Radius 4.0 → Area 50.26548245743669
      Radius 5.0 → Area 78.53981633974483
  14. 扩展题:设计 Tom 类,实现与电脑猜拳功能(石头 0、剪刀 1、布 2),记录输赢次数(Homework14.java)

概述

多个程序员开发同一个项目时,可能出现类名冲突(如两个 Dog 类),通过包区分。

包(Package):是 Java 中管理类和接口的核心机制,本质是命名空间(Namespace),用于解决类名冲突组织代码结构控制访问权限三大核心问题。它是 Java 模块化编程的基础,贯穿于类的定义、导入、编译、运行全流程。每个类都属于一个包,若未显式声明包,则属于默认包(无名包)。

包的本质命名空间与代码组织

  • 命名空间:通过包名给类名添加前缀,避免不同包下的类名冲突(如 java.util.Datejava.sql.Date);
  • 代码组织:按功能模块划分包,使代码结构清晰,便于维护(如 com.demo.controller 存放控制器、com.demo.service 存放服务类)。

包的命名规则

Java 官方规定包名采用反向域名命名法,确保全球唯一:

  • 只能包含数字/字母/下划线/点.,不能以数字开头,不能有关键字/保留字;
  • 全部小写,避免大小写敏感问题;
  • 以公司 / 组织的反向域名开头,后接功能模块;
  • 不同层级用点.分隔。
java
// 公司项目
package com.baidu.xxx
// 个人项目
package com.demo.controller
package com.demo.service

基本语法

包的声明

包的声明语法格式

java
package 包名;

核心规则

  1. 必须是源文件的第一条语句:在 package 语句之前不能有任何代码(可以有注释);
  2. 一个源文件只能有一个 package 语句:一个类只能属于一个包;
  3. 包名与目录结构一致:编译后,包名对应操作系统的目录结构(如 com.demo 对应 com/demo 目录)。

示例

java
// 正确:package 是第一条语句
package com.demo.model;

public class User {
    private String name;
}

默认包

若源文件中未显式声明 package 语句,则该类属于默认包(无名包)。

默认包特点

  • 包名为空,类名无前缀;
  • 不同源文件的默认包类之间可直接访问(默认访问权限);
  • 不推荐使用:易导致类名冲突,且无法导入其他包的类(JDK 1.4 后限制)。

示例

java
// 无 package 语句,属于默认包
public class DefaultPackageClass {}

快速入门

java
// 包com.xiaoming下的Dog类
package com.xiaoming;
public class Dog {}
java
// 包com.xiaoqiang下的Dog类
package com.xiaoqiang;
public class Dog {}
java
// 测试类
package com.use;
import com.xiaoming.Dog; // 导入指定包的类
public class Test {
    public static void main(String[] args) {
        Dog dog1 = new Dog(); // com.xiaoming.Dog
        com.xiaoqiang.Dog dog2 = new com.xiaoqiang.Dog(); // 全类名访问
    }
}

三大核心作用

包的三大核心作用

  1. 作用 1:解决类名冲突(核心作用)

    不同包下的类可以同名,通过全限定类名(包名 + 类名)区分。

    java
    // 1. 导入 java.util 包的 Date 类
    import java.util.Date;
    // 2. 导入 java.sql 包的 Date 类,用别名区分
    import java.sql.Date as SqlDate;
    
    public class DateDemo {
        public static void main(String[] args) {
            Date utilDate = new Date(); // java.util.Date
            java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis()); // 全限定类名
        }
    }
  2. 作用 2:组织代码结构(模块化编程)

    按功能模块划分包,使代码结构清晰,符合 “高内聚、低耦合” 的设计原则。

    plaintext
    com.demo
    ├── controller  // 控制器(处理请求)
    ├── service     // 服务层(业务逻辑)
    ├── dao         // 数据访问层(操作数据库)
    ├── model       // 实体类(数据模型)
    ├── util        // 工具类(通用功能)
    └── config      // 配置类(全局配置)
  3. 作用 3:控制访问权限(与访问修饰符配合)

    包与访问修饰符(defaultprotected)配合,实现类的访问控制:

    • default 访问权限(无修饰符):仅同包内的类可访问;
    • protected 访问权限:同包内的类 + 不同包的子类可访问。
    java
    // 包 com.demo.a
    package com.demo.a;
    
    // default 权限的类:仅同包可访问
    class DefaultClass {
        public void show() {
            System.out.println("default 类的方法");
        }
    }
    
    public class TestA {
        public static void main(String[] args) {
            // 同包的测试类:可访问 DefaultClass
            DefaultClass dc = new DefaultClass(); // 合法
            dc.show();
        }
    }
    java
    // 不同包的测试类:不可访问 DefaultClass
    package com.demo.b;
    
    import com.demo.a.DefaultClass; // 错误:DefaultClass 是 default 权限,无法导入
    
    public class TestB {
        public static void main(String[] args) {
            // DefaultClass dc = new DefaultClass(); // 错误:无法访问
        }
    }

类的导入

通过 import 语句引入其他包的类,避免在代码中重复写全限定类名,简化代码编写。

语法格式

语法格式

  1. 格式 1:导入单个类

    java
    import 包名.类名;
  2. 格式 2:导入包下所有类(通配符 *

    java
    import 包名.*;
  3. 格式 3:静态导入(导入类的静态成员)

    java
    // 导入单个静态成员
    import static 包名.类名.静态成员名;
    // 导入所有静态成员
    import static 包名.类名.*;

核心规则

  1. import 语句的位置:必须在 package 语句之后,类定义之前;

  2. 一个源文件可有多条 import 语句:按需导入多个包的类;

  3. 通配符 * 仅导入当前包的类:不导入子包的类(如 import com.demo.* 不导入 com.demo.controller.User);

  4. 默认导入:JVM 自动导入 java.lang.* 包下的所有类(如 StringSystemObject),无需显式导入。

示例

  1. 单个类导入与全限定类名

    java
    package com.demo.test;
    
    // 1. 导入 java.util 包的 ArrayList 类
    import java.util.ArrayList;
    
    public class ImportDemo {
        public static void main(String[] args) {
            // 2. 简化写法:直接使用类名
            ArrayList<String> list1 = new ArrayList<>();
    
            // 全限定类名:不导入也可使用,但繁琐
            java.util.HashMap<String, String> map = new java.util.HashMap<>();
        }
    }
  2. 通配符导入

    java
    package com.demo.test;
    
    // 1. 导入 java.util 包下所有类
    import java.util.*;
    
    public class ImportWildcardDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>(); // 2. 合法
            HashMap<String, String> map = new HashMap<>(); // 3. 合法
        }
    }
  3. 静态导入

    java
    package com.demo.test;
    
    // 1. 静态导入 Math 类的 PI 常量和 sqrt 方法
    import static java.lang.Math.PI;
    import static java.lang.Math.sqrt;
    
    public class StaticImportDemo {
        public static void main(String[] args) {
            // 2. 简化写法:直接使用静态成员
            System.out.println(PI); // 3.141592653589793
            System.out.println(sqrt(4)); // 2.0
    
            // 3. 不静态导入的写法:Math.PI、Math.sqrt(4)
        }
    }

解决类名冲突

当导入的两个包中有同名类时,需通过全限定类名区分,或使用导入别名(JDK 10+ 支持)。

方式 1:全限定类名

java
package com.demo.test;

// 1. 导入 java.util.Date
import java.util.Date;

public class DateConflictDemo {
    public static void main(String[] args) {
        // 2. java.util.Date
        Date utilDate = new Date();

        // 3. 用全限定类名区分 java.sql.Date
        java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
    }
}

方式 2:导入别名

java
package com.demo.test;

// 给 java.sql.Date 起别名 SqlDate
import java.sql.Date as SqlDate;
import java.util.Date;

public class DateAliasDemo {
    public static void main(String[] args) {
        Date utilDate = new Date();
        SqlDate sqlDate = new SqlDate(System.currentTimeMillis()); // 使用别名
    }
}

包的编译与运行

Java 编译器要求包名与目录结构完全一致,否则会编译失败或运行时找不到类。

命令行编译与运行步骤

  1. 步骤 1:创建目录结构

    假设包名是 com.demo,需创建目录 com/demo,将 Hello.java 放入该目录:

    plaintext
    └── com
        └── demo
            └── Hello.java
  2. 步骤 2:编写代码(Hello.java)

    java
    package com.demo;
    
    public class Hello {
        public void sayHello() {
            System.out.println("Hello, Package!");
        }
    }
  3. 步骤 3:编译代码(javac 命令)

    根目录com 目录的上级目录)执行编译命令,使用 :

    sh
    javac -d bin com/demo/Hello.java
    • -d bin:指定编译后类文件的输出目录(保持包结构),如bin/com/demo/Hello.class
  4. 步骤 4:运行代码(java 命令)

    根目录执行运行命令,使用全限定类名(包名 + 类名):

    sh
    java -cp bin com.demo.Hello
    • -cp bin:指定类路径为 bin 目录;
    • com.demo.Hello:全限定类名,不能写 Hellocom/demo/Hello

IDE 包管理

Java 核心包

JDK 提供了大量内置包,包含常用的类和接口,以下是最核心的几个:

  1. java.lang:核心包,自动导入,包含 StringObjectSystemMath 等类;

  2. java.util:工具包,包含 ArrayListHashMapDateRandomScanner 等类;

  3. java.io:输入输出包,包含文件操作、流操作相关类(如 FileInputStream);

  4. java.net:网络编程包,包含 SocketServerSocket 等类;

  5. java.sql:数据库编程包,包含 ConnectionStatementResultSet 等接口;

  6. java.awt/swing:图形界面编程包,包含窗口、按钮等组件类。